package com.luych.toolbox.common.service.configuration;

import com.luych.toolbox.common.feign.intf.base.service.UserFeign;
import com.luych.toolbox.common.feign.intf.gateway.center.WebSocketFeign;
import com.luych.toolbox.common.feign.param.base.service.UserParam;
import com.luych.toolbox.common.feign.view.base.service.UserView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.Optional;

@Configuration
@RestController
public class AuthenticationConfiguration {

    private static final String LOGIN = "/login";
    private static final Logger logger = LoggerFactory.getLogger(AuthenticationConfiguration.class);

    @GetMapping(LOGIN)
    public ResponseEntity<String> unauthorized() {
        return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
    }

    @Bean
    public UserDetailsService userDetailsService(UserFeign userFeign) {
        return loginName -> {
            UserView userView = userFeign.getByLoginName(loginName);
            if (userView == null) {
                throw new UsernameNotFoundException("Can't find user with loginName: " + loginName);
            }
            return userView;
        };
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler(UserFeign userFeign) {
        return (request, response, authentication) -> {
            Object principal = authentication.getPrincipal();
            UserView userView = (UserView) principal;
            UserParam userParam = new UserParam();
            BeanUtils.copyProperties(userView, userParam);
            userParam.setLastLoginTime(new Date());
            userParam.setLastLoginIp(request.getRemoteAddr());
            userFeign.modify(userView.getId(), userParam);
            logger.info("user:" + userView.getUserName() + " login success");
            response.setStatus(HttpStatus.OK.value());
        };
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            logger.error("user login failed", exception);
            throw exception;
        };
    }

    @Bean
    public LogoutSuccessHandler logoutSuccessHandler(UserFeign userFeign, WebSocketFeign webSocketFeign) {
        return (request, response, authentication) -> {
            if (authentication == null) {
                response.setStatus(HttpStatus.NO_CONTENT.value());
            } else {
                Object principal = authentication.getPrincipal();
                UserView userView = (UserView) principal;
                webSocketFeign.close(userView.getId());
                logger.info("user:" + userView.getUserName() + " logout success");
                response.setStatus(HttpStatus.OK.value());
            }
        };
    }

    @Bean
    public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(
            AuthenticationSuccessHandler successHandler,
            AuthenticationFailureHandler failureHandler,
            LogoutSuccessHandler logoutSuccessHandler) {
        return new WebSecurityConfigurerAdapter() {
            @Override
            public void configure(HttpSecurity httpSecurity) throws Exception {
                httpSecurity
                        .headers().frameOptions().sameOrigin()
                        .and().formLogin().loginPage(LOGIN).successHandler(successHandler).failureHandler(failureHandler)
                        .and().logout().logoutSuccessHandler(logoutSuccessHandler)
                        .and().csrf().disable();
            }
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public AuditorAware<String> auditorAware(UserFeign userFeign) {
        return () -> {
            SecurityContext securityContext = SecurityContextHolder.getContext();
            if (securityContext == null) return Optional.empty();
            Authentication authentication = securityContext.getAuthentication();
            if (authentication == null) return Optional.empty();
            if (!authentication.isAuthenticated()) return Optional.empty();
            Object principal = authentication.getPrincipal();
            if (principal == null) return Optional.empty();
            if (!(principal instanceof UserView)) return Optional.empty();
            UserView userView = (UserView) principal;
            return Optional.ofNullable(userView.getId());
        };
    }
}
